//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#ifndef __ELASTOS_8237a_H__
#define __ELASTOS_8237a_H__

typedef physaddr_t  busaddr_t;

#define PHYSADDR_TO_BUSADDR(pa)     (pa)
#define BUSADDR_TO_PHYSADDR(ba)     (ba)

#define VIRTADDR_TO_BUSADDR(va)     PHYSADDR_TO_BUSADDR(PHYSADDR(va))
#define BUSADDR_TO_VIRTADDR(ba)     VIRTADDR(BUSADDR_TO_PHYSADDR(ba))

const uint_t c_uNumberOfDmaChannels = 8;

// I/O port for DMA registers
#define DMA1_COMMAND_REGISTER               ((ioport_t)0x0008)
#define DMA2_COMMAND_REGISTER               ((ioport_t)0x00d0)

#define DMA1_STATUS_REGISTER                ((ioport_t)0x0008)
#define DMA2_STATUS_REGISTER                ((ioport_t)0x00d0)

#define DMA1_MODE_REGISTER                  ((ioport_t)0x000b)
#define DMA2_MODE_REGISTER                  ((ioport_t)0x00d6)

#define DMA1_CLEAR_BYTE_POINTER_REGISTER    ((ioport_t)0x000c)
#define DMA2_CLEAR_BYTE_POINTER_REGISTER    ((ioport_t)0x00d8)

#define DMA1_RESET_REGISTER                 ((ioport_t)0x000d)
#define DMA2_RESET_REGISTER                 ((ioport_t)0x00da)

#define DMA1_CLEAR_MASK_REGISTER            ((ioport_t)0x000e)
#define DMA2_CLEAR_MASK_REGISTER            ((ioport_t)0x00dc)

#define DMA1_SINGLE_MASK_REGISTER           ((ioport_t)0x000a)
#define DMA2_SINGLE_MASK_REGISTER           ((ioport_t)0x00d4)

#define DMA1_WRITE_ALL_MASK_REGISTER        ((ioport_t)0x000f)
#define DMA2_WRITE_ALL_MASK_REGISTER        ((ioport_t)0x00de)

#define DMA1_REQUEST_REGISTER               ((ioport_t)0x0009)
#define DMA2_REQUEST_REGISTER               ((ioport_t)0x00d2)

#define DMA_BASE_ADDRESS_REGISTER(no)       c_dmaBaseAddressRegisters[no]
#define DMA_TRANSFER_COUNT_REGISTER(no)     c_dmaTransferCountRegisters[no]
#define DMA_PAGE_REGISTER(no)               c_dmaPageRegisters[no]

const ioport_t c_dmaBaseAddressRegisters[8] =
    {0x0000, 0x0002, 0x0004, 0x0006, 0x00c0, 0x00c4, 0x00c8, 0x00cc};

const ioport_t c_dmaTransferCountRegisters[8] =
    {0x0001, 0x0003, 0x0005, 0x0007, 0x00c2, 0x00c6, 0x00ca, 0x00ce};

const ioport_t c_dmaPageRegisters[8] =
    {0x0087, 0x0083, 0x0081, 0x0082, 0x008f, 0x008b, 0x0089, 0x008a};

// Mode Register
enum {
    // Mode Select
    ModeRegister_DemandMode                 = 0x00,
    ModeRegister_SingleMode                 = 0x40,
    ModeRegister_BlockMode                  = 0x80,
    ModeRegister_CascadeMode                = 0xc0, // channel 4 only

    ModeRegister_IncrementAddress           = 0x00,
    ModeRegister_DecrementAddress           = 0x20,

    ModeRegister_DisableAutoInit            = 0x00,
    ModeRegister_EnableAutoInit             = 0x10,

    // Data Transfer Type
    ModeRegister_VerifyTransfer             = 0x00,
    // from I/O device to memory
    ModeRegister_WriteTransfer              = 0x04,
    // from memory to I/O device
    ModeRegister_ReadTransfer               = 0x08,
    // DMA Channel Select: Bit[0, 1]
};

// Operations
INLINE void EnableDmaChannel(uint_t uNo)
{
    if (uNo <= 3)
        Outb(DMA1_SINGLE_MASK_REGISTER, uNo);
    else
        Outb(DMA2_SINGLE_MASK_REGISTER, uNo & 3);
}

INLINE void DisableDmaChannel(uint_t uNo)
{
    if (uNo <= 3)
        Outb(DMA1_SINGLE_MASK_REGISTER, __8BIT(2) | uNo);
    else
        Outb(DMA2_SINGLE_MASK_REGISTER, __8BIT(2) | (uNo & 3));
}

INLINE void ClearDmaBytePointer(uint_t uNo)
{
    if (uNo <= 3)
        Outb(DMA1_CLEAR_BYTE_POINTER_REGISTER, 0);
    else
        Outb(DMA2_CLEAR_BYTE_POINTER_REGISTER, 0);
}

INLINE bool_t IsDmaModeAvailable(DmaTransferMode mode)
{
    if (DmaTransferMode_DeviceToMemory == mode ||
        DmaTransferMode_MemoryToDevice == mode) {
        return TRUE;
    }

    return FALSE;
}

INLINE void SetDmaMode(uint_t uNo, DmaTransferMode mode)
{
    register uint8_t u8Mode = 0;

    switch (mode) {
        case DmaTransferMode_DeviceToMemory:
            u8Mode = ModeRegister_SingleMode | ModeRegister_WriteTransfer;
            break;
        case DmaTransferMode_MemoryToDevice:
            u8Mode = ModeRegister_SingleMode | ModeRegister_ReadTransfer;
            break;
        default:
            assert0();
    }

    if (uNo <= 3)
        Outb(DMA1_MODE_REGISTER, u8Mode | uNo);
    else
        Outb(DMA2_MODE_REGISTER, u8Mode | (uNo & 3));
}

INLINE void SetDmaChannelPage(uint_t uNo, uint8_t u8Page)
{
    if (uNo <= 3)
        Outb(DMA_PAGE_REGISTER(uNo), u8Page);
    else
        Outb(DMA_PAGE_REGISTER(uNo), u8Page & 0xfe);
}

INLINE void SetDmaChannelBaseAddress(uint_t uNo, busaddr_t addr)
{
    SetDmaChannelPage(uNo, addr >> 16);

    if (uNo <= 3) {
        Outb(DMA_BASE_ADDRESS_REGISTER(uNo), addr & 0xff);
        Outb(DMA_BASE_ADDRESS_REGISTER(uNo), (addr >> 8) & 0xff);
    }
    else {
        Outb(DMA_BASE_ADDRESS_REGISTER(uNo), (addr >> 1) & 0xff);
        Outb(DMA_BASE_ADDRESS_REGISTER(uNo), (addr >> 9) & 0xff);
    }
}

INLINE void SetDmaChannelTransferCount(uint_t uNo, uint32_t count)
{
    count--;

    if (uNo <= 3) {
        Outb(DMA_TRANSFER_COUNT_REGISTER(uNo), count & 0xff);
        Outb(DMA_TRANSFER_COUNT_REGISTER(uNo), (count >> 8) & 0xff);
    }
    else {
        Outb(DMA_TRANSFER_COUNT_REGISTER(uNo), (count >> 1) & 0xff);
        Outb(DMA_TRANSFER_COUNT_REGISTER(uNo), (count >> 9) & 0xff);
    }
}

INLINE uint32_t GetDmaTransferCount(uint_t uNo)
{
    uint16_t count = 1 + Inb(DMA_TRANSFER_COUNT_REGISTER(uNo));
    count += Inb(DMA_TRANSFER_COUNT_REGISTER(uNo)) << 8;

    return (uNo <= 3)? count : (count << 1);
}

#endif // __ELASTOS_8237a_H__
